<?php

namespace app\index\controller;

use AlibabaCloud\SDK\Sts\V20150401\Models\AssumeRoleRequest;
use AlibabaCloud\SDK\Sts\V20150401\Sts;
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
use app\common\library\BLiveTools;
use app\common\library\TechminoTools;
use app\index\model\Room as RoomModel;
use Curl\Curl;
use Darabonba\OpenApi\Models\Config;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Cache;
use think\facade\Env;
use think\helper\Str;
use think\response\Json;

/**
 * 方块相关
 * Class Fish
 * @package app\index\controller
 */
class Tetr extends Controller
{
    /**
     * 获取方块玩法列表
     * @return Json
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function list(): Json
    {
        $model = new RoomModel;
        $list = $model->getAll($this->request->param(), false);
        return $this->renderSuccess(compact('list'));
    }

    /**
     * 获取方块玩法列表
     * @return Json
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function page(): Json
    {
        $model = new RoomModel;
        $list = $model->getList($this->request->param());
        return $this->renderSuccess(compact('list'));
    }

    /**
     * 获取房间信息
     * @param $roomId
     * @return Json
     */
    public function roomInfo($roomId): Json
    {
        $tools = new BLiveTools([]);
        $anchor = $tools->ApiGetAnchorInRoom($roomId);
        $video = $tools->ApiGetRoomPlayInfo($roomId);
        $playGame = $tools->ApiGetRoomGameInfo($roomId);

        // 转换头像为base64
        if ($anchor && array_key_exists('face', $anchor)) {
            $faceUrl = $anchor['face'];
            $faceContent = file_get_contents($faceUrl);
            $faceBase64 = base64_encode($faceContent);
            $anchor['face'] = 'data:image/jpeg;base64,' . $faceBase64;
        }

        // 整理信息
        $info = [
            'anchor'   => $anchor,
            'video'    => $video,
            'playGame' => $playGame,
        ];

        return $this->renderSuccess($info);
    }

    /**
     * 通过携带BV号的链接获取视频播放地址
     * @param $bvUrl
     * @return Json
     */
    public function bvGetPlayerUrlInfo($bvUrl): Json
    {
        // 做一次目录下超过30天的视频回收
        if (is_dir(public_path('stream'))) {
            // 扫描目录超过30天的文件
            $dir = public_path('stream');
            $files = scandir($dir);
            foreach ($files as $file) {
                if ($file == '.' || $file == '..') {
                    continue;
                }
                $filePath = $dir . '/' . $file;
                if (is_file($filePath)) {
                    $fileTime = filectime($filePath);
                    if (time() - $fileTime > 2592000) {
                        unlink($filePath);
                    }
                }
            }
        }

        $tools = new BLiveTools([]);

        // 正则验证链接是否是哔哩哔哩视频链接
        if (preg_match('/www\.bilibili\.com\/video\/([a-zA-Z0-9]+)/', $bvUrl, $matches)) {
            $bvId = $matches[1];
            $responseData = [];
            $videoInfo = $tools->ApiQueryVideoCid($bvId);

            // 无法读取到视频信息
            if (!$videoInfo) {
                return $this->renderError('视频信息获取失败');
            }

            // 视频分集信息
            foreach ($videoInfo as $index => $episode) {
                $videoData = $episode;
                $stream = $tools->ApiQueryVideoStream($videoData['cid'], $bvId);

                // 读取视频流信息
                if (!$stream) {
                    return $this->renderError('视频流信息获取失败');
                }

                // 视频流信息合并到信息中
                if (empty($stream['dash']['video'][0]['baseUrl'])) {
                    return $this->renderError("视频分集<{$index}>不存在有效的视频链接");
                }

                // 做视频链接的MD5并写入缓存
                $videoData['url_hash'] = md5($stream['dash']['video'][0]['baseUrl']);
                $videoData['bvid'] = $bvId;
                Cache::set('VIDEO_HASH:' . $videoData['url_hash'], array_merge($episode, ['bvid' => $bvId, 'url' => $stream['dash']['video'][0]['baseUrl']]), 3600);
                $responseData[] = $videoData;
            }

            // 返回获取到的视频数据
            return $this->renderSuccess($responseData);
        }

        return $this->renderError('视频地址解析错误');
    }

    /**
     * 通过视频链接的md5哈希串获取视频流
     * @param $hash
     * @return Json|void
     */
    public function bvHashGetStreamFileUrl($hash)
    {
        // 下载避免超时设置接口超时时间
        set_time_limit(0);

        // 缓存是否存在
        if (!Cache::has('VIDEO_HASH:' . $hash)) {
            return $this->renderError('视频地址无效');
        } else {

            $videoInfo = Cache::get('VIDEO_HASH:' . $hash);

            // 文件夹是否存在
            if (!is_dir(public_path('stream'))) {
                mkdir(public_path('stream'));
            }

            // 视频写入到文件中
            $fileName = $videoInfo['bvid'] . '_P' . $videoInfo['page'] . '.mp4';
            $fullPath = public_path('stream') . $fileName;

            // 如果文件已经存在直接返回地址避免多次下载
            if (file_exists($fullPath)) {
                $fileUrl = $this->request->domain() . '/stream/' . $fileName;
                return $this->renderSuccess(['url' => $fileUrl]);
            }

            // 否则打开文件开始下载
            $file = fopen($fullPath, 'w');

            // 文件直接下载到本地
            $curl = new Curl();
            $curl->setHeader('Referer', 'https://www.bilibili.com/video/');
            $curl->setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
            $curl->setOpt(CURLOPT_FILE, $file);
            $stream = $curl->get($videoInfo['url']);
            fclose($file);

            if ($stream->isSuccess()) {
                $fileUrl = $this->request->domain() . '/stream/' . $fileName;
                return $this->renderSuccess(['url' => $fileUrl]);
            } else {
                return $this->renderError('视频流获取失败');
            }
        }
    }

    /**
     * 随机生成铁壳场地
     * @param int $line 生成函数
     * @param int $emptyRate 占空比
     * @param int $colorful
     * @param int $type 1 随机地形 2 等棍地形 3-中间突出
     * @return Json
     */
    public function buildTechminoRandomField(int $line = 15, int $emptyRate = 30, int $colorful = 0, int $type = 1): Json
    {
        // 生成一个完全完整的场地
        $fullField = function () use ($line, $colorful) {
            $fieldArr = [];
            for ($i = 0; $i < $line; $i++) {
                $row = [];
                for ($j = 0; $j < 10; $j++) {
                    if ($colorful) {
                        $row[] = rand(0x02, 0x11); // 彩色模式
                    } else {
                        $row[] = 0x16; // 垃圾块模式
                    }
                }
                $fieldArr[] = $row;
            }
            return $fieldArr;
        };
        $fieldArr = $fullField();

        // 随机地形
        if ($type === 1) {
            for ($i = 0; $i < $line; $i++) {
                for ($j = 0; $j < 10; $j++) {
                    // 按照比率掏空这个场地
                    if (rand(0, 100) < $emptyRate) {
                        $fieldArr[$i][$j] = 0x01;
                    }
                }
            }
        }

        // 等棍地形
        if ($type === 2) {
            foreach (range(0, 9) as $colIndex) {
                // 本列需要掏空的深度
                $depth = rand(0, intval($line * $emptyRate / 100));
                foreach ($fieldArr as $rowIndex => $rowArr) {
                    if ($rowIndex < $depth) {
                        $fieldArr[$rowIndex][$colIndex] = 0x01;
                    }
                }
            }
        }

        // 检查已生成的每一行是否都有空格了
        for ($i = 0; $i < $line; $i++) {
            $hasEmpty = false;
            for ($j = 0; $j < 10; $j++) {
                if ($fieldArr[$i][$j] == 0x01) {
                    $hasEmpty = true;
                    break;
                }
            }
            if (!$hasEmpty) {
                // 从该行中随机填充一个空格
                $fieldArr[$i][rand(0, 9)] = 0x01;
            }
        }

        // 场地信息
        return $this->renderSuccess(['field' => TechminoTools::encodeField($fieldArr), 'map' => $fieldArr]);
    }

    /**
     * 从矩阵生成铁壳字符串
     * @return Json
     */
    public function matrixToTechmino(): Json
    {
        // 遍历该矩阵生成铁壳矩阵
        $fieldArr = [];
        $matrix = $this->request->param()['matrix'];
        foreach ($matrix as $rowIdx => $row) {
            $fieldArr[] = [];
            foreach ($row as $cell) {
                if ($cell == 0) {
                    $fieldArr[$rowIdx][] = 0x01;
                } else {
                    $fieldArr[$rowIdx][] = 0x16;
                }
            }
        }

        return $this->renderSuccess(['field' => TechminoTools::encodeField($fieldArr), 'map' => $fieldArr]);
    }

    /**
     * 上传视频并生成复盘码
     * @return Json
     */
    public function uploadGetReviewCode(): Json
    {
        // 验证内测码
        $invCode = $this->request->param('invCode');
        if ($invCode != 'ABC123456') {
            return $this->renderError('内测码无效');
        }

        // 秘钥配置
        $config = new Config([
            'accessKeyId'     => Env::get('oss.oss_access_key', ''),
            'accessKeySecret' => Env::get('oss.oss_access_secret', ''),
        ]);

        // 创建STS客户端
        $config->endpoint = "sts.cn-heyuan.aliyuncs.com";
        $client = new Sts($config);

        // 临时授权凭证
        $assumeRoleRequest = new AssumeRoleRequest([
            // roleArn填写步骤2获取的角色ARN
            "roleArn"         => "acs:ram::1860582882737683:role/kuaifouram",
            // roleSessionName用于自定义角色会话名称，用来区分不同的令牌
            "roleSessionName" => "KuaifouUploader",
            // durationSeconds用于设置临时访问凭证有效时间单位为秒，最小值为900，最大值以当前角色设定的最大会话时间为准。本示例指定有效时间为3000秒。
            "durationSeconds" => 3600
        ]);

        // 获取临时访问令牌
        $runtime = new RuntimeOptions([]);
        $result = $client->assumeRoleWithOptions($assumeRoleRequest, $runtime);

        // 生成复盘码
        $fileName = Str::random(16);
        $reviewCode = 'Review:' . $fileName;

        return $this->renderSuccess([
            'region'          => 'oss-cn-heyuan',
            'bucket'          => 'kuaifou',
            'fileName'        => $fileName,
            'reviewCode'      => $reviewCode,
            'callbackUrl'     => $this->request->domain() . '/index.php?s=/index/tetr/uploadSuccessCallback',
            'stsToken'        => $result->body->credentials->securityToken,
            'accessKeyId'     => $result->body->credentials->accessKeyId,
            'accessKeySecret' => $result->body->credentials->accessKeySecret,
            'expiration'      => $result->body->credentials->expiration,
        ]);
    }

    /**
     * 上传成功回调
     * @return Json
     */
    public function uploadSuccessCallback(): Json
    {
        file_put_contents('upload.log', json_encode($this->request->param()));
        return $this->renderSuccess();
    }
}
